As a beginner in Liberty Basic I don't know an awful lot about it, but one thing I do know is the drawbmp command is very slow. This is all right if you have a latest-and-greatest 2.4ghz computer, but my poor little 200mhz computer shows off this lack of speed quite spectacularly. For games programming, this lack of speed is a huge disadvantage. This is not true of LB sprite animation for games. See Editor's Notes If you have to redraw the screen 25 times a second, it's no good if each frame takes a whole second to redraw. See Editor's Notes So I figured there must be a better way, and there is. Introducing bit blocking!
Unfortunately, Liberty Basic doesn't have any built in bit blocking functionality so we have to use the windows bit blocking routines instead. This is not entirely true. See Editor's Notes Windows doesn't draw on graphics boxes though, it draws on device contexts. A device context is a sort of interface that windows uses to draw graphics on a wide range of devices using the same commands. To get our device contexts all we have to do is create a normal graphics box and use the windows API function "GetDC" to get the device context handle.
Here's how to bit block.
At this point I'll insert some sample code. The bmp used can be found in the Liberty Basic BMP folder. This program proves that bit blocking is faster than the drawbmp command.
[WindowSetup]
NOMAINWIN
WindowWidth = 433: WindowHeight = 410
UpperLeftX = INT((DisplayWidth-WindowWidth)/2)
UpperLeftY = INT((DisplayHeight-WindowHeight)/2)
[ControlSetup]
graphicbox #main.graphicbox1, 15, 46, 400, 100
graphicbox #main.graphicbox2, 12, 180, 400, 100
statictext #main.statictext1, "Source grpahics box", 17, 13, 386, 20
statictext #main.statictext2, "Destination graphics box", 11, 155, 403, 21
button #main.button1, "GO!", [button1.click], UL, 146, 288, 128, 27
textbox #main.textbox1, 269, 328, 140, 19
textbox #main.textbox2, 269, 354, 140, 19
statictext #main.statictext3, "Bit Blocking took", 185, 330, 81, 21
statictext #main.statictext4, "Drawbmp took", 194, 357, 76, 19
statictext #main.statictext5, "Results: This shows the time", 2, 326, 143, 13
statictext #main.statictext6, "it takes to copy source to destination", 3, 340, 178, 16
statictext #main.statictext7, "100 times using the two methods", 2, 358, 174, 13
Open "Bit blocking and drawbmp speed test" for window as #main
#main "trapclose [quit]"
#main.graphicbox1 "down; fill white; flush"
#main.graphicbox2 "down; fill white; flush"
#main.textbox1 "!disable"
#main.textbox2 "!disable"
'get windows handles for graphics boxes
hSource=hwnd(#main.graphicbox1)
hDest=hwnd(#main.graphicbox2)
[loop]
Wait
[quit]
'release device contexts
CallDLL#user32,"ReleaseDC",_
hSource As long,_ 'graphicbox handle
hSourceDC As long,_ 'DC handle
result As long
CallDLL#user32,"ReleaseDC",_
hDest As long,_ 'graphicbox handle
hDestDC As long,_ 'DC handle
result As long
' the end
close #main : END
[button1.click]
'load a bmp to copy
loadbmp "bar","bmp\bar.bmp"
'stamp the bitmap onto source in preparation for bit blocking
#main.graphicbox1 "drawbmp bar 0 0"
'get device context handles for bit blocking
CallDLL #user32, "GetDC",_
hSource As long,_ 'handle of graphicbox1
hSourceDC As long 'returns device context
CallDLL #user32, "GetDC",_
hDest As long,_ 'handle of graphicbox1
hDestDC As long 'returns device context
'set bit blocking rastor operation
dwRop=hexdec("00CC0020")
'check time
starttime=time$("ms")
'bit block 100 times at random positions
for cnt=1 to 100
'find position
x=int(rnd(1)*312) + 1
y=int(rnd(1)*35) + 1
'bit block!
calldll #gdi32, "BitBlt", _
hDestDC As Long, _
x As Long, _
y As Long, _
88 As Long, _
65 As Long, _
hSourceDC As Long, _
0 As Long, _
0 As Long, _
dwRop As Long, _
result as Long
next
'how long did it take?
duntime=time$("ms")-starttime
'tell the user
#main.textbox1 str$(duntime)
'now lets try the same thing with the drawbmp command
'check time
starttime=time$("ms")
for cnt=1 to 100
'find position
x=int(rnd(1)*312) + 1
y=int(rnd(1)*35) + 1
#main.graphicbox2 "drawbmp bar "+str$(x)+" "+str$(y)
next
'find out how long it took
duntime=time$("ms")-starttime
'tell the user the shocking truth
#main.textbox2 str$(duntime)
'go back to the main loop
goto [loop]
And now you most probably want an explanation of how I did the bit-blocking thing. Bit blocking takes a block of pixels from the source device context and puts them on the destination device context. So, instead of using drawbmp to redraw your screen, you could put your graphics elements (tiles, sprites etc) into the source device context and then bit block them onto the destination device context when it comes time to redraw the screen. If you don't want the user to see all the graphics elements sitting in an on screen device context, just bear with me for a while because I'll cover it soon.
Here's what the program did:
This article will focus on bit blocking, not device contexts. If you can't figure out how to create device contexts from the code you will need to seek an explanation elsewhere. Now lets look at the bit blocking more closely. First of all the rastor operation. With bit blocking you can achieve some interesting and useful effects by using the rastor operations. These operations are all hex numbers, so you have to convert them to decimal with the hexdec function before LB can use them. The main rastor operations are:
00000042: This operation draws a black rectangle on the source device context. Not particularly useful
001100A6: Combines the colours of the source and destination rectangles by using the Boolean OR operator and then inverts the resultant colour.
00330008: copies the source to the destination, then inverts the colours.
00440328: combines the inverted colours of the destination rectangle with the colours of the source rectangle by using the Boolean AND operator
00550009: Inverts the destination rectangle
005A0049: Combines the colours of the specified pattern with the colours of the destination rectangle by using the Boolean XOR operator.
00660046: Combines the colours of the source and destination rectangles by using the Boolean XOR operator. If you do this to the same spot twice, the destination will return to its original state.
008800C6: Combines the colours of the source and destination rectangles by using the Boolean AND operator.
00BB0226: Merges the colours of the inverted source rectangle with the colours of the destination rectangle by using the Boolean OR operator.
00C000CA: Merges the colours of the inverted source rectangle with the colours of the destination rectangle by using the Boolean OR operator.
00CC0020: Copies the source rectangle directly to the destination device context, no fancy Boolean operators, just a straight copy.
00EE0086: Combines the colours of the source and destination rectangles by using the Boolean OR operator.
00F00021: Copies the specified pattern into the destination bitmap.
00FB0A09: Combines the colours of the pattern with the colours of the inverted source rectangle by using the Boolean OR operator. The result of this operation is combined with the colours of the destination rectangle by using the Boolean OR operator.
00FF0062: This operation draws a white rectangle.
I'll leave it to you to find uses for all these operations.
The bit block function is quite simple:
calldll #gdi32, "BitBlt", _
hDestDC As Long, _
x As Long, _
y As Long, _
w As Long, _
h As Long, _
hSourceDC As Long, _
sx As Long, _
sy As Long, _
dwRop As Long, _
result as Long
hDestDC is the handle to the destination device context
x and y are the x and y positions on the destination device context that you want to copy to.
w and h are the width and height of the rectangle to be copied
hSourceDC is the handle of the source device context
sx is the x position of the rectangle to copy on the source device context
sy is the y position of the rectangle to copy on the source device context
dwRop is the rastor operation. If the function returns 0 in the result variable, that means an error occurred.
Now this bit blocking may be fast and everything, but there is that annoying source device context visible on the screen. To make a professional looking game we need to put that source device context somewhere else. We could hide it behind another window but that's a bit primitive, especially when windows has a much better solution. Create a device context in the memory! A memory device context emulates a real device context. You can draw to it using gdi functions and bit blocking, it is invisible and much faster to draw to. You could draw each frame of you game on the memory device context, then quickly bit block it onto your graphics box. You can have any number of device contexts, but if you're short on RAM like me you'll have to be careful not to create too many of them. I normally create a memory device context for every graphic element and one for a screen buffer. I bit block from the element memory device contexts to the screen buffer, then bit block the screen buffer to the graphics box.
So how do we create a memory device context? With the "CreateCompatibleDC" API of course! This API returns the handle to the new memory device context. Here's what it looks like:
CallDLL #gdi32,"CreateCompatibleDC",_ hDC As long,_ 'device context of graphicbox memDC As long 'returns memory device context
hDC is the device context handle to a graphics box. The memory device context will exactly emulate that device context. memDC is the handle to the new memory device context. When I'm using lots of memory device contexts, I usually put them in an array for reference, like this:
dim memdcarray(10) CallDLL #gdi32,"CreateCompatibleDC",_ hDC As long,_ 'device context of graphicbox memDC As long 'returns memory device context memdcarray(0)=memDC CallDLL #gdi32,"CreateCompatibleDC",_ hDC As long,_ 'device context of graphicbox memDC As long 'returns memory device context memdcarray(1)=memDC
When I need the handle to that memory device context, I can just grab it from the array back into memDC variable.
Before you can use your new memory device context you need to put a bitmap into it. The device context will take on the same dimensions as the bitmap you load into it. If your memory DC will be used to hold a specific bitmap like a graphic element for a game, you can load that bitmap with the LB loadbmp command. If you are just creating some sort of buffer to temporarily hold graphics like a screen buffer, the best method is to create a blank bitmap and use that. The first method is easiest. load the bmp with the loadbmp command, get the windows handle with the hbmp command, and then use the "SelectObject" API to put the bitmap into the device context. Example:
loadbmp "bmp","C:\IDONOTEXISTONYOURCOMPUTER.BMP"
bmphandle=hwnd("bmp")
CallDLL #gdi32,"SelectObject",_
memDC As long,_ 'handle of memory DC
bmphandle As long,_ 'handle of LB bitmap
oldObject As long 'returns handle of previous
'bitmap in DC
The other method requires you to use another API to create a blank bitmap. This is the "CreateCompatibleBitmap" API. This API creates a blank bitmap with the same colour specifications as an existing device context. This example shows how it's done:
calldll #gdi32, "CreateCompatibleBitmap", _ hDC As Long, _ X As Long, _ Y As Long, _ hBuffer as Long CallDLL #gdi32,"SelectObject",_ memDC As long,_ 'handle of memory DC hBuffer As long,_ 'handle of bitmap oldObject As long 'returns handle of previous bitmap
In this example hDC is the device context handle of a graphics box, X is the width of the bitmap you want to create and y is the height. hBuffer is the windows handle for the bitmap.
Once you have your memory device contexts there you can do what you like with them, just remember to use the "DeleteDC" API to get rid of them before ending your program otherwise you could crash your computer. Here's an example of the "DeleteDC" API:
CallDLL #gdi32, "DeleteDC",_ memDC As long,_ 'memory DC handle r As Boolean
if r=true (not 0) then device context was deleted, otherwise it is either still there or was never there in the first place.
I hope I've covered the whole thing here, if not you shouldn't have too much trouble figuring it out for yourself. If a 15 year old (me) can do it, anyone can do it.
By Callum Lowcay
Editor's Notes - Alyce Watson
Callum mentions that DRAWBMP is too slow for games, and he recommends Bit Blocking. In fact, the sprite engine that is part of Liberty BASIC uses these techniques to create sprite animation, and it is very fast. If you prefer to have more control over game graphics, then be sure to study and use Callum's methods. (I wrote the original sprite code, that Carl improved and incorporated into Liberty BASIC, which is how I know the methods used.) Issue #108 has an article called "Sprite in a Box" that uses similar methods to draw a sprite directly into a graphicbox using API calls. [Sprite in a Box]
This technique gives you the ability to draw a portion of a bitmap, if you desire to do so.
Please note that API graphics do not "stick" to the graphicbox. After the window has been obscured and then displayed again, the graphics that were obscured are not repainted. For a method to make API graphics stick, see the Liberty BASIC helpfile sprite section on "Flushing Sprite Graphics."
In earlier versions of Windows, a program could create no more than five device contexts at one time. This may not still be in force. I have not been able to find a number for current versions of Windows. It is best not to create large numbers of device contexts, however.
The raster operation codes can be obtained as outlined by Callum. They can also be used directly as Windows Constants recognized by LB. The raster operation used in the code above can also be passed as _SRCCOPY
_BLACKNESS Fills the destination rectangle using the color associated with index 0 in the physical palette. (This color is black for the default physical palette.)
_DSTINVERT Inverts the destination rectangle.
_MERGECOPY Merges the colors of the source rectangle with the specified pattern by using the Boolean AND operator.
_MERGEPAINT Merges the colors of the inverted source rectangle with the colors of the destination rectangle by using the Boolean OR operator.
_NOTSRCCOPY Copies the inverted source rectangle to the destination.
_NOTSRCERASE Combines the colors of the source and destination rectangles by using the Boolean OR operator and then inverts the resultant color.
_PATCOPY Copies the specified pattern into the destination bitmap.
_PATINVERT Combines the colors of the specified pattern with the colors of the destination rectangle by using the Boolean XOR operator.
_PATPAINT Combines the colors of the pattern with the colors of the inverted source rectangle by using the Boolean OR operator. The result of this operation is combined with the colors of the destination rectangle by using the Boolean OR operator.
_SRCAND Combines the colors of the source and destination rectangles by using the Boolean AND operator.
_SRCCOPY Copies the source rectangle directly to the destination rectangle.
SRCERASE Combines the inverted colors of the destination rectangle with the colors of the source rectangle by using the Boolean AND operator.
_SRCINVERT Combines the colors of the source and destination rectangles by using the Boolean XOR operator.
_SRCPAINT Combines the colors of the source and destination rectangles by using the Boolean OR operator.
_WHITENESS Fills the destination rectangle using the color associated with index 1 in the physical palette. (This color is white for the default physical palette.)